﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SerializationUtils.cs" company="">
//   
// </copyright>
// <summary>
//   The serialization utils.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

#region License

/*
 **************************************************************
 *  Author: Rick Strahl 
 *          © West Wind Technologies, 2008 - 2009
 *          http://www.west-wind.com/
 * 
 * Created: 09/08/2008
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 **************************************************************  
*/
#endregion

//using System.Reflection;
namespace Skymate.ComponentModel
{
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Text;
    using System.Xml;
    using System.Xml.Serialization;

    using Fasterflect;

    // Serialization specific code

    /// <summary>
    ///     The serialization utils.
    /// </summary>
    internal static class SerializationUtils
    {
        /// <summary>
        /// Serializes an object instance to a file.
        /// </summary>
        /// <param name="instance">
        /// the object instance to serialize
        /// </param>
        /// <param name="fileName">
        /// </param>
        /// <param name="binarySerialization">
        /// determines whether XML serialization or binary serialization is used
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        public static bool SerializeObject(object instance, string fileName, bool binarySerialization)
        {
            var retVal = true;

            if (!binarySerialization)
            {
                XmlTextWriter writer = null;
                try
                {
                    var serializer = new XmlSerializer(instance.GetType());

                    // Create an XmlTextWriter using a FileStream.
                    Stream fs = new FileStream(fileName, FileMode.Create);
                    writer = new XmlTextWriter(fs, new UTF8Encoding());
                    writer.Formatting = Formatting.Indented;
                    writer.IndentChar = ' ';
                    writer.Indentation = 3;

                    // Serialize using the XmlTextWriter.
                    serializer.Serialize(writer, instance);
                }
                catch (Exception ex)
                {
                    Debug.Write("SerializeObject failed with : " + ex.Message, "West Wind");
                    retVal = false;
                }
                finally
                {
                    if (writer != null)
                    {
                        writer.Close();
                    }
                }
            }
            else
            {
                Stream fs = null;
                try
                {
                    var serializer = new BinaryFormatter();
                    fs = new FileStream(fileName, FileMode.Create);
                    serializer.Serialize(fs, instance);
                }
                catch
                {
                    retVal = false;
                }
                finally
                {
                    if (fs != null)
                    {
                        fs.Close();
                    }
                }
            }

            return retVal;
        }

        /// <summary>
        /// Overload that supports passing in an XML TextWriter.
        /// </summary>
        /// <remarks>
        /// Note the Writer is not closed when serialization is complete
        ///     so the caller needs to handle closing.
        /// </remarks>
        /// <param name="instance">
        /// object to serialize
        /// </param>
        /// <param name="writer">
        /// XmlTextWriter instance to write output to
        /// </param>
        /// <param name="throwExceptions">
        /// Determines whether false is returned on failure or an exception is thrown
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        public static bool SerializeObject(object instance, XmlTextWriter writer, bool throwExceptions)
        {
            var retVal = true;

            try
            {
                var serializer = new XmlSerializer(instance.GetType());

                // Create an XmlTextWriter using a FileStream.
                writer.Formatting = Formatting.Indented;
                writer.IndentChar = ' ';
                writer.Indentation = 3;

                // Serialize using the XmlTextWriter.
                serializer.Serialize(writer, instance);
            }
            catch (Exception ex)
            {
                Debug.Write(
                    "SerializeObject failed with : " + ex.GetBaseException().Message + "\r\n"
                    + (ex.InnerException != null ? ex.InnerException.Message : string.Empty), 
                    "West Wind");

                if (throwExceptions)
                {
                    throw;
                }

                retVal = false;
            }

            return retVal;
        }

        /// <summary>
        /// Serializes an object into an XML string variable for easy 'manual' serialization
        /// </summary>
        /// <param name="instance">
        /// object to serialize
        /// </param>
        /// <param name="xmlResultString">
        /// resulting XML string passed as an out parameter
        /// </param>
        /// <returns>
        /// true or false
        /// </returns>
        public static bool SerializeObject(object instance, out string xmlResultString)
        {
            return SerializeObject(instance, out xmlResultString, false);
        }

        /// <summary>
        /// Serializes an object into a string variable for easy 'manual' serialization
        /// </summary>
        /// <param name="instance">
        /// </param>
        /// <param name="xmlResultString">
        /// Out parm that holds resulting XML string
        /// </param>
        /// <param name="throwExceptions">
        /// If true causes exceptions rather than returning false
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        public static bool SerializeObject(object instance, out string xmlResultString, bool throwExceptions)
        {
            xmlResultString = string.Empty;
            var ms = new MemoryStream();

            var writer = new XmlTextWriter(ms, new UTF8Encoding());

            if (!SerializeObject(instance, writer, throwExceptions))
            {
                ms.Close();
                return false;
            }

            xmlResultString = Encoding.UTF8.GetString(ms.ToArray(), 0, (int)ms.Length);

            ms.Close();
            writer.Close();

            return true;
        }

        /// <summary>
        /// Serializes an object instance to a file.
        /// </summary>
        /// <param name="instance">
        /// the object instance to serialize
        /// </param>
        /// <param name="resultBuffer">
        /// The result Buffer.
        /// </param>
        /// <param name="throwExceptions">
        /// The throw Exceptions.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        public static bool SerializeObject(object instance, out byte[] resultBuffer, bool throwExceptions = false)
        {
            var retVal = true;

            MemoryStream ms = null;
            try
            {
                var serializer = new BinaryFormatter();
                ms = new MemoryStream();
                serializer.Serialize(ms, instance);
            }
            catch (Exception ex)
            {
                Debug.Write("SerializeObject failed with : " + ex.GetBaseException().Message, "West Wind");
                retVal = false;

                if (throwExceptions)
                {
                    throw;
                }
            }
            finally
            {
                if (ms != null)
                {
                    ms.Close();
                }
            }

            resultBuffer = ms.ToArray();

            return retVal;
        }

        /// <summary>
        /// Serializes an object to an XML string. Unlike the other SerializeObject overloads
        ///     this methods *returns a string* rather than a bool result!
        /// </summary>
        /// <param name="instance">
        /// </param>
        /// <param name="throwExceptions">
        /// Determines if a failure throws or returns null
        /// </param>
        /// <returns>
        /// null on error otherwise the Xml String.
        /// </returns>
        /// <remarks>
        /// If null is passed in null is also returned so you might want
        ///     to check for null before calling this method.
        /// </remarks>
        public static string SerializeObjectToString(object instance, bool throwExceptions = false)
        {
            var xmlResultString = string.Empty;

            if (!SerializeObject(instance, out xmlResultString, throwExceptions))
            {
                return null;
            }

            return xmlResultString;
        }

        /// <summary>
        /// The serialize object to byte array.
        /// </summary>
        /// <param name="instance">
        /// The instance.
        /// </param>
        /// <param name="throwExceptions">
        /// The throw exceptions.
        /// </param>
        /// <returns>
        /// The <see cref="byte[]"/>.
        /// </returns>
        public static byte[] SerializeObjectToByteArray(object instance, bool throwExceptions = false)
        {
            byte[] byteResult = null;

            if (!SerializeObject(instance, out byteResult))
            {
                return null;
            }

            return byteResult;
        }

        /// <summary>
        /// Deserializes an object from file and returns a reference.
        /// </summary>
        /// <param name="fileName">
        /// name of the file to serialize to
        /// </param>
        /// <param name="objectType">
        /// The Type of the object. Use typeof(yourobject class)
        /// </param>
        /// <param name="binarySerialization">
        /// determines whether we use Xml or Binary serialization
        /// </param>
        /// <returns>
        /// Instance of the deserialized object or null. Must be cast to your object type
        /// </returns>
        public static object DeSerializeObject(string fileName, Type objectType, bool binarySerialization)
        {
            return DeSerializeObject(fileName, objectType, binarySerialization, false);
        }

        /// <summary>
        /// Deserializes an object from file and returns a reference.
        /// </summary>
        /// <param name="fileName">
        /// name of the file to serialize to
        /// </param>
        /// <param name="objectType">
        /// The Type of the object. Use typeof(yourobject class)
        /// </param>
        /// <param name="binarySerialization">
        /// determines whether we use Xml or Binary serialization
        /// </param>
        /// <param name="throwExceptions">
        /// determines whether failure will throw rather than return null on failure
        /// </param>
        /// <returns>
        /// Instance of the deserialized object or null. Must be cast to your object type
        /// </returns>
        public static object DeSerializeObject(
            string fileName, 
            Type objectType, 
            bool binarySerialization, 
            bool throwExceptions)
        {
            object instance = null;

            if (!binarySerialization)
            {
                XmlReader reader = null;
                XmlSerializer serializer = null;
                FileStream fs = null;
                try
                {
                    // Create an instance of the XmlSerializer specifying type and namespace.
                    serializer = new XmlSerializer(objectType);

                    // A FileStream is needed to read the XML document.
                    fs = new FileStream(fileName, FileMode.Open);
                    reader = new XmlTextReader(fs);

                    instance = serializer.Deserialize(reader);
                }
                catch (Exception ex)
                {
                    if (throwExceptions)
                    {
                        throw;
                    }

                    var message = ex.Message;
                    return null;
                }
                finally
                {
                    if (fs != null)
                    {
                        fs.Close();
                    }

                    if (reader != null)
                    {
                        reader.Close();
                    }
                }
            }
            else
            {
                BinaryFormatter serializer = null;
                FileStream fs = null;

                try
                {
                    serializer = new BinaryFormatter();
                    fs = new FileStream(fileName, FileMode.Open);
                    instance = serializer.Deserialize(fs);
                }
                catch
                {
                    return null;
                }
                finally
                {
                    if (fs != null)
                    {
                        fs.Close();
                    }
                }
            }

            return instance;
        }

        /// <summary>
        /// Deserialize an object from an XmlReader object.
        /// </summary>
        /// <param name="reader">
        /// </param>
        /// <param name="objectType">
        /// </param>
        /// <returns>
        /// The <see cref="object"/>.
        /// </returns>
        public static object DeSerializeObject(XmlReader reader, Type objectType)
        {
            var serializer = new XmlSerializer(objectType);
            var Instance = serializer.Deserialize(reader);
            reader.Close();

            return Instance;
        }

        /// <summary>
        /// The de serialize object.
        /// </summary>
        /// <param name="xml">
        /// The xml.
        /// </param>
        /// <param name="objectType">
        /// The object type.
        /// </param>
        /// <returns>
        /// The <see cref="object"/>.
        /// </returns>
        public static object DeSerializeObject(string xml, Type objectType)
        {
            var reader = new XmlTextReader(xml, XmlNodeType.Document, null);
            return DeSerializeObject(reader, objectType);
        }

        /// <summary>
        /// Deseializes a binary serialized object from  a byte array
        /// </summary>
        /// <param name="buffer">
        /// </param>
        /// <param name="objectType">
        /// </param>
        /// <param name="throwExceptions">
        /// </param>
        /// <returns>
        /// The <see cref="object"/>.
        /// </returns>
        public static object DeSerializeObject(byte[] buffer, Type objectType, bool throwExceptions = false)
        {
            BinaryFormatter serializer = null;
            MemoryStream ms = null;
            object Instance = null;

            try
            {
                serializer = new BinaryFormatter();
                ms = new MemoryStream(buffer);
                Instance = serializer.Deserialize(ms);
            }
            catch
            {
                if (throwExceptions)
                {
                    throw;
                }

                return null;
            }
            finally
            {
                if (ms != null)
                {
                    ms.Close();
                }
            }

            return Instance;
        }

        /// <summary>
        /// Returns a string of all the field value pairs of a given object.
        ///     Works only on non-statics.
        /// </summary>
        /// <param name="instance">
        /// </param>
        /// <param name="separator">
        /// </param>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        public static string ObjectToString(object instance, string separator, ObjectToStringTypes type)
        {
            var fi = instance.GetType().Fields();

            var output = string.Empty;

            if (type == ObjectToStringTypes.Properties || type == ObjectToStringTypes.PropertiesAndFields)
            {
                foreach (var property in instance.GetType().Properties())
                {
                    try
                    {
                        output += property.Name + ":" + instance.GetPropertyValue(property.Name) + separator;
                    }
                    catch
                    {
                        output += property.Name + ": n/a" + separator;
                    }
                }
            }

            if (type == ObjectToStringTypes.Fields || type == ObjectToStringTypes.PropertiesAndFields)
            {
                foreach (var field in fi)
                {
                    try
                    {
                        output = output + field.Name + ": " + instance.GetFieldValue(field.Name) + separator;
                    }
                    catch
                    {
                        output = output + field.Name + ": n/a" + separator;
                    }
                }
            }

            return output;
        }
    }

    /// <summary>
    ///     The object to string types.
    /// </summary>
    public enum ObjectToStringTypes
    {
        /// <summary>
        ///     The properties.
        /// </summary>
        Properties, 

        /// <summary>
        ///     The properties and fields.
        /// </summary>
        PropertiesAndFields, 

        /// <summary>
        ///     The fields.
        /// </summary>
        Fields
    }
}